/*
 * Unidata Platform Community Edition
 * Copyright (c) 2013-2020, UNIDATA LLC, All rights reserved.
 * This file is part of the Unidata Platform Community Edition software.
 *
 * Unidata Platform Community Edition is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Unidata Platform Community Edition is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
 */
package org.unidata.mdm.rest.v1.dq.core.converter;

import java.util.Arrays;
import java.util.Objects;
import java.util.stream.Collectors;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.unidata.mdm.core.util.AttributeUtils;
import org.unidata.mdm.dq.core.type.constant.ArrayValueConstantType;
import org.unidata.mdm.dq.core.type.constant.SingleValueConstantType;
import org.unidata.mdm.dq.core.type.model.source.constant.AbstractArrayValueConstant;
import org.unidata.mdm.dq.core.type.model.source.constant.AbstractSingleValueConstant;
import org.unidata.mdm.dq.core.type.model.source.constant.ArrayValueConstant;
import org.unidata.mdm.dq.core.type.model.source.constant.BooleanSingleValueConstant;
import org.unidata.mdm.dq.core.type.model.source.constant.CleanseFunctionConstant;
import org.unidata.mdm.dq.core.type.model.source.constant.DateArrayValueConstant;
import org.unidata.mdm.dq.core.type.model.source.constant.DateSingleValueConstant;
import org.unidata.mdm.dq.core.type.model.source.constant.IntegerArrayValueConstant;
import org.unidata.mdm.dq.core.type.model.source.constant.IntegerSingleValueConstant;
import org.unidata.mdm.dq.core.type.model.source.constant.NumberArrayValueConstant;
import org.unidata.mdm.dq.core.type.model.source.constant.NumberSingleValueConstant;
import org.unidata.mdm.dq.core.type.model.source.constant.SingleValueConstant;
import org.unidata.mdm.dq.core.type.model.source.constant.StringArrayValueConstant;
import org.unidata.mdm.dq.core.type.model.source.constant.StringSingleValueConstant;
import org.unidata.mdm.dq.core.type.model.source.constant.TimeArrayValueConstant;
import org.unidata.mdm.dq.core.type.model.source.constant.TimeSingleValueConstant;
import org.unidata.mdm.dq.core.type.model.source.constant.TimestampArrayValueConstant;
import org.unidata.mdm.dq.core.type.model.source.constant.TimestampSingleValueConstant;
import org.unidata.mdm.rest.system.converter.Converter;
import org.unidata.mdm.rest.v1.dq.core.ro.constant.AbstractConstantRO;
import org.unidata.mdm.rest.v1.dq.core.ro.constant.ArrayConstantRO;
import org.unidata.mdm.rest.v1.dq.core.ro.constant.ArrayValueRO;
import org.unidata.mdm.rest.v1.dq.core.ro.constant.SingleConstantRO;
import org.unidata.mdm.rest.v1.dq.core.ro.constant.SingleValueRO;

/**
 * @author Mikhail Mikhailov on Mar 1, 2021
 */
public class ConstantConverter extends Converter<CleanseFunctionConstant, AbstractConstantRO> {
    /**
     * Constructor.
     * @param to
     * @param from
     */
    public ConstantConverter() {
        super(ConstantConverter::convert, ConstantConverter::convert);
    }

    private static AbstractConstantRO convert(CleanseFunctionConstant source) {

        if (source.isEmpty()) {
            return null;
        }

        AbstractConstantRO retval = null;
        if (source.isSingle()) {

            SingleValueConstant<?> svc = source.getSingle();
            SingleValueRO target = new SingleValueRO();
            target.setType(svc.getType().name());
            target.setValue(svc.getValue());

            retval = new SingleConstantRO(target);
        } else if (source.isArray()) {

            ArrayValueConstant<?> svc = source.getArray();
            ArrayValueRO target = new ArrayValueRO();
            target.setType(svc.getType().name());
            target.setValues(CollectionUtils.isNotEmpty(svc.getValues()) ? svc.getValues().toArray() : ArrayUtils.EMPTY_OBJECT_ARRAY);

            retval = new ArrayConstantRO(target);
        }

        return retval;
    }

    private static CleanseFunctionConstant convert(AbstractConstantRO source) {

        switch (source.getType()) {
        case SINGLE:
            SingleConstantRO sro = (SingleConstantRO) source;
            return new CleanseFunctionConstant()
                    .withSingle(convertSingleValue(sro.getSingle()));
        case ARRAY:
            ArrayConstantRO aro = (ArrayConstantRO) source;
            return new CleanseFunctionConstant()
                    .withArray(convertArrayValue(aro.getArray()));
        default:
            break;
        }

        return null;
    }


    private static AbstractArrayValueConstant<?, ?> convertArrayValue(ArrayValueRO source) {

        if (Objects.isNull(source)) {
            return null;
        }

        AbstractArrayValueConstant<?, ?> val = null;
        ArrayValueConstantType type = ArrayValueConstantType.valueOf(source.getType());
        switch (type) {
        case DATE:
            val = new DateArrayValueConstant()
                .withValues(ArrayUtils.isEmpty(source.getValues())
                        ? null
                        : Arrays.stream(source.getValues())
                            .map(AttributeUtils::toDate)
                            .filter(Objects::nonNull)
                            .collect(Collectors.toList()));
            break;
        case INTEGER:
            val = new IntegerArrayValueConstant()
                .withValues(ArrayUtils.isEmpty(source.getValues())
                        ? null
                        : Arrays.stream(source.getValues())
                            .map(AttributeUtils::toLong)
                            .filter(Objects::nonNull)
                            .collect(Collectors.toList()));
            break;
        case NUMBER:
            val = new NumberArrayValueConstant()
                .withValues(ArrayUtils.isEmpty(source.getValues())
                    ? null
                    : Arrays.stream(source.getValues())
                        .map(AttributeUtils::toDouble)
                        .filter(Objects::nonNull)
                        .collect(Collectors.toList()));
            break;
        case STRING:
            val = new StringArrayValueConstant()
                .withValues(ArrayUtils.isEmpty(source.getValues())
                    ? null
                    : Arrays.stream(source.getValues())
                        .map(AttributeUtils::toString)
                        .filter(Objects::nonNull)
                        .collect(Collectors.toList()));
            break;
        case TIME:
            val = new TimeArrayValueConstant()
                .withValues(ArrayUtils.isEmpty(source.getValues())
                    ? null
                    : Arrays.stream(source.getValues())
                        .map(AttributeUtils::toTime)
                        .filter(Objects::nonNull)
                        .collect(Collectors.toList()));
            break;
        case TIMESTAMP:
            val = new TimestampArrayValueConstant()
                .withValues(ArrayUtils.isEmpty(source.getValues())
                    ? null
                    : Arrays.stream(source.getValues())
                        .map(AttributeUtils::toTimestamp)
                        .filter(Objects::nonNull)
                        .collect(Collectors.toList()));
            break;
        default:
            break;
        }

        return val;
    }

    private static AbstractSingleValueConstant<?, ?> convertSingleValue(SingleValueRO source) {

        if (Objects.isNull(source)) {
            return null;
        }

        AbstractSingleValueConstant<?, ?> val = null;
        SingleValueConstantType type = SingleValueConstantType.valueOf(source.getType());
        switch (type) {
        case DATE:
            val = new DateSingleValueConstant()
                .withValue(AttributeUtils.toDate(source.getValue()));
            break;
        case INTEGER:
            val = new IntegerSingleValueConstant()
                .withValue(AttributeUtils.toLong(source.getValue()));
            break;
        case NUMBER:
            val = new NumberSingleValueConstant()
                .withValue(AttributeUtils.toDouble(source.getValue()));
            break;
        case STRING:
            val = new StringSingleValueConstant()
                .withValue(AttributeUtils.toString(source.getValue()));
            break;
        case TIME:
            val = new TimeSingleValueConstant()
                .withValue(AttributeUtils.toTime(source.getValue()));
            break;
        case TIMESTAMP:
            val = new TimestampSingleValueConstant()
                .withValue(AttributeUtils.toTimestamp(source.getValue()));
            break;
        case BOOLEAN:
            val = new BooleanSingleValueConstant()
                .withValue(AttributeUtils.toBoolean(source.getValue()));
        break;
        default:
            break;
        }

        return val;
    }
}
